package tools

import (
	"reflect"
	"strings"
	"time"
)

type StructTool struct{}
type NameTool struct{}
type TimeTool struct{}

//Transfer struct's fields to a map by using reflect, zero field and ID field will be ignored;
//the keys's name in map will be snake style, instead of Camel style in struct
func (s *StructTool) StructToMap(data interface{}) map[string]interface{} {
	v := reflect.ValueOf(data)
	t := reflect.TypeOf(data)
	result := make(map[string]interface{})

	for i := 0; i < v.NumField(); i++ {
		if !v.Field(i).IsZero() && t.Field(i).Name != "ID" {
			result[nameTool.CamelToSnakeString(t.Field(i).Name)] = v.Field(i).Interface()
		}
	}

	return result
}

//Transfer struct's fields to a map by using reflect, zero field and ID field will be ignored;
//the keys's name in map will be snake style, instead of Camel style in struct
func (s *StructTool) MapToStruct(m map[string]interface{}, st interface{}) {
	v := reflect.ValueOf(st).Elem()

	for i := 0; i < v.NumField(); i++ {
		fieldInfo := v.Type().Field(i)
		if val, ok := m[nameTool.CamelToSnakeString((fieldInfo.Name))]; !ok {
			continue
		} else {
			v.FieldByName(fieldInfo.Name).Set(reflect.ValueOf(val))
		}
	}
}

//Transfer camel string to snake string
func (n *NameTool) CamelToSnakeString(s string) string {
	if s == "ID" {
		return "id"
	}

	data := make([]byte, 0, len(s)*2)
	j := false
	num := len(s)
	for i := 0; i < num; i++ {
		d := s[i]
		// or通过ASCII码进行大小写的转化
		// 65-90（A-Z），97-122（a-z）
		//判断如果字母为大写的A-Z就在前面拼接一个_
		if i > 0 && d >= 'A' && d <= 'Z' && j {
			data = append(data, '_')
		}
		if d != '_' {
			j = true
		}
		data = append(data, d)
	}
	//ToLower把大写字母统一转小写
	return strings.ToLower(string(data[:]))
}

//Transfer snake string to camel string
func (n *NameTool) SnakeToCamelString(s string) string {
	if s == "id" {
		return "ID"
	}

	data := make([]byte, 0, len(s))
	j := false
	k := false
	num := len(s) - 1
	for i := 0; i <= num; i++ {
		d := s[i]
		if k == false && d >= 'A' && d <= 'Z' {
			k = true
		}
		if d >= 'a' && d <= 'z' && (j || k == false) {
			d = d - 32
			j = false
			k = true
		}
		if k && d == '_' && num > i && s[i+1] >= 'a' && s[i+1] <= 'z' {
			j = true
			continue
		}
		data = append(data, d)
	}
	return string(data[:])
}

//time.Time to nano second timestamp
func (t *TimeTool) TimeToNanoStamp(tt time.Time) int64 {
	return tt.UnixNano()
}

//nano second timestamp to time.Time
func (t *TimeTool) NanoStampToTime(timestamp int64) time.Time {
	return time.Unix(0, int64(timestamp))
}

//Second timestamp to string
func (t *TimeTool) StampToString(timeStamp int64) string {
	tt := time.Unix(int64(timeStamp), 0)
	ttStr := tt.Format("2006/01/02 15:04:05")

	return ttStr
}

//string to local time.Time
func (t *TimeTool) StringToTime(timestr string) (time.Time, error) {

	tt, err := time.ParseInLocation("2006-01-02 15:04:05", timestr, time.Local)

	if err != nil {
		return time.Time{}, err
	}

	return tt, nil
}

//local time string to second timestamp
func (t *TimeTool) StringToStamp(timestr string) (int64, error) {
	tt, err := t.StringToTime(timestr)

	if err != nil {
		return 0, err
	}

	return tt.Unix(), nil
}
